#!/usr/bin/perl
#copyright Ian Baker 2007

use strict;
use Device::SerialPort;
use Time::HiRes qw (time usleep);

my $if0 = new Device::SerialPort('/dev/ttyS0', 0);

$if0->baudrate(19200);
$if0->parity("odd");
$if0->databits(8);
$if0->stopbits(2);
$if0->handshake('none');

die "$!" unless $if0->write_settings;

my $if1 = new Device::SerialPort('/dev/ttyS1', 0);

$if1->baudrate(19200);
$if1->parity("odd");
$if1->databits(8);
$if1->stopbits(2);
$if1->handshake('none');

die "$!" unless $if1->write_settings;

# sensor reading blocky things.
my @sensors1 = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,1);
#my @sensors0a = (17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,32);
#my @sensors0b = (33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,48);

my %sensor_addresses = (
			1 => 0,
			2 => 1,
			3 => 2,
			4 => 3,
			5 => 4,
			6 => 5,
			7 => 6,
			8 => 7,
			9 => 8,
			10 => 9,
			11 => 10,
			12 => 11,
			13 => 12,
			14 => 13,
			15 => 14,
			16 => 15,
);
##			17 => 0,
##			18 => 1,
##			19 => 2,
##			20 => 3,
##			21 => 4,
##			22 => 5,
##			23 => 6,
##			24 => 7,
##			25 => 8,
##			26 => 9,
##			27 => 10,
##			28 => 11,
##			29 => 12,
##			30 => 13,
##			31 => 14,
##			32 => 15,

##			33 => 16,
##			34 => 17,
##			35 => 18,
##			36 => 19,
##			37 => 20,
##			38 => 21,
##			39 => 22,
##			40 => 23,
##			41 => 24,
##			42 => 25,
##			43 => 26,
##			44 => 27,
##			45 => 28,
##			46 => 29,
##			47 => 30,
##			48 => 31,
##			);

my %sensor_groups = (
		     1 => [1,17,18],
		     2 => [2,19,20],
		     3 => [3,21,22],
		     4 => [4,23,24],
		     5 => [5,25,26],
		     6 => [6,27,28],
		     7 => [7,29,30],
		     8 => [8,31,32],
		     9 => [9,41,42],
		     10 => [10,35,36],
		     11 => [11,37,38],
		     12 => [12,39,40],
		     13 => [13,33,34],
		     14 => [14,43,44],
		     15 => [15,45,46],
		     16 => [16,47,48],
		     );

# invert the sensor_groups hash for quick lookups.
my %sensor_group_map = map { 
    my $group = $_;
    map { ($_ => $group); } @{$sensor_groups{$group}};
} sort keys %sensor_groups;

my %effect_addresses = (
		     1 => [32,33,34,35],
		     2 => [36,37,38,39],
		     3 => [40,41,42,43],
		     4 => [44,45,46,47],
		     5 => [48,49,50,51],
		     6 => [52,53,54,55],
		     7 => [56,57,58,59],
		     16 => [60,61,62,63],
		     9 => [64,65,66,67],
		     10 => [68,69,70,71],
		     11 => [72,73,74,75],
		     12 => [76,77,78,79],
		     13 => [80,81,82,83],
		     14 => [84,85,86,87],
		     15 => [88,89,90,91],
		     8 => [92,93,94,95],
		     );

# for storing the current state of each effect.
my %effect_state;

# for storing the last fire time for each effect.
my %last_fired;

# store the sensor's current value.
my %sensor_current;

# how much above baseline to fire?
my $high_threshold = 3;
my $low_threshold = 50;
my $min_firing_time = .200;  #100ms


# calibrate the sensors.
my %baseline;
my %baseline_values;
$| = 1;
print "Calibrating...\n";
calibrate($if1, @sensors1);
print "uno!\n";
#calibrate($if0, @sensors0b);
#print "dos!\n";
#calibrate($if1, @sensors1);
#print "tres!\n";

print "averages:\n";
foreach my $sensor (sort { $a <=> $b } keys %baseline_values) {
  my $sum;
  foreach(@{$baseline_values{$sensor}}) {
    $sum += $_->{val};
  }
  
  $baseline{$sensor} = $sum / scalar(@{$baseline_values{$sensor}});
  print "$sensor: $baseline{$sensor}\n";
}

my %time_baseline;

# find out how long each sensor holds the high number.
foreach my $sensor (sort { $a <=> $b } keys %baseline_values) {
  my @high_blocks;  # not really necessary to build this, but oh well.
  my $last_val;
  my @current_block;
  foreach(@{$baseline_values{$sensor}}) {
    if(($last_val + $high_threshold) >= $baseline{$sensor}) {
      unless(($_->{val} + $high_threshold) >= $baseline{$sensor}) {
        # was in a block. now, not.
        push @high_blocks, \@current_block;
        undef @current_block;
#        print "block end\n";
      }
    }

    if(($_->{val} + $high_threshold) >= $baseline{$sensor}) {
      # in a block, save the value.
#      print "add $_->{val} ($baseline{$sensor})\n";
      push @current_block, $_;
    }
    
    $last_val = $_->{val};
  }

  my $high_time;
  # find the largest time.
  foreach my $block (@high_blocks) {
    next unless scalar(@{$block}) >= 2;
    my $this_time = $block->[(scalar(@{$block})) - 1]->{time} - $block->[0]->{time};
    $high_time = $this_time if($this_time > $high_time);
  }

  if($high_time < 0.042) {
    $time_baseline{$sensor} = 0.042;
  } else {
    $time_baseline{$sensor} = $high_time;
  }
  
  print "$sensor: $time_baseline{$sensor}\n";

}

print "\nDone.\n";
my $i = 0;

my $last_time = time();
my %timed;
my $exit = 0;
my @to_write;
my $last_batch = 'b';
while(!$exit) {
    # my @read_sensors0;
    my @read_sensors1 = @sensors1;

    $i++;
    
    # jankity...
    # if($last_batch eq 'a') {
    #   @read_sensors0 = @sensors0b;
    #   $last_batch = 'b';
    # } else {
    #   @read_sensors0 = @sensors0a;
    #   $last_batch = 'a';
    # }
    
    # translate sensor ids into sensor addresses.
    # my @write_sensors0 = map { $sensor_addresses{$_} } @read_sensors0;
    my @write_sensors1 = map { $sensor_addresses{$_} } @read_sensors1;
    
    # write whatever needs it between read cycles
    if(@to_write) {
	print "********** write: " . join(',', @to_write) . "\n";
	$if0->write(pack('C*', @to_write));
	$if0->read(255);
	undef @to_write;
    }
    
    # send the reads.
    # $if0->write(pack('C*', @write_sensors0));
    $if1->write(pack('C*', @write_sensors1));
    
    # pop @read_sensors0; #remove that last discarded value.
    pop @read_sensors1;
    
    my $first0 = 1;
    my $first1 = 1;


    
    while(@read_sensors1) {
	#   my ($count0, $raw_read0) = $if0->read(255);
	my ($count1, $raw_read1) = $if1->read(255);
	
	next unless ($count1);
	
	# jankity...
	my $pick = 1;
	foreach my $read ($raw_read1) {
	    foreach my $value (unpack('C*', $read)) {
		my $curr;
		if($first1) {
		    undef $first1;
		    next;
		}
		
		$curr = shift(@read_sensors1);

		push @{$timed{$curr}}, { val => $value, time => time() };
#		print $value . "/" . $baseline{$curr} . "\n" if $curr == 8;
		
		# trim @timed
		if(scalar(@{$timed{$curr}}) > 35) {
		    shift(@{$timed{$curr}});
		} else {
		    next;
		}
		
		
		# if the value is high enough for low effect, we can assume it's correct.
#		if($value >= ($baseline{$curr} + $low_threshold + 8)) {
#		    # low effect start firing (hysteresis)
#		    print "low start $curr, val $value, tdif " . (time() - $_->{time}) . ", bl $baseline{$curr}, time $time_baseline{$curr}\n";
#		    print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
#		    fire_the_fucking_flamethrowers_oh_my_god($curr, 'low');
#		    next;
#		}
#		
#		# effect was already on
#		if(($value >= ($baseline{$curr} + $low_threshold)) && $effect_state{$curr} == 'low') {
#		    # low effect continue firing
#		    print "low cont  $curr, val $value, tdif " . (time() - $_->{time}) . ", bl $baseline{$curr}, time $time_baseline{$curr}\n";
#		    print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
#		    fire_the_fucking_flamethrowers_oh_my_god($curr, 'low');
#		    next;
#		}
		
		# search through the history and make sure the effect has been high long enough
		# (ie. filter out transient noise)
		my $off = 1;
		foreach(reverse @{$timed{$curr}}) {
		    # not high enough.
		    last unless($_->{val} >= ($baseline{$curr} + $high_threshold));
		    
		    # have we made it?
		    if(($_->{time} + $time_baseline{$curr}) <= time()) {
			# values stayed high for long enough, go ahead and fire.
			print "high      $curr, val $_->{val}, tdif " . (time() - $_->{time}) . ", bl $baseline{$curr}, time $time_baseline{$curr}\n";
			print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";
			$off = 0;
			fire_the_fucking_flamethrowers_oh_my_god($curr, 'low');
			fire_the_fucking_flamethrowers_oh_my_god($curr, 'high');
			last;
		    }
		}
		
		next unless $off;
		
		fire_the_fucking_flamethrowers_oh_my_god($curr, 'off');
#                print "off       $curr, val $_->{val}, tdif " . (time() - $_->{time}) . ", bl $baseline{$curr}, time $time_baseline{$curr}\n";
#                print "values: " . join(', ', map { $_->{val} } @{$timed{$curr}}) . "\n";

	    }
	}
    }
}

sub fire_the_fucking_flamethrowers_oh_my_god {
    my($effect, $state) = @_;
    
    # effect array offsets:
    # 0 - high off
    # 1 - high on
    # 2 - low off
    # 3 - low on

    
    if($effect_state{$effect} eq 'off') {
	if($state eq 'low') {
	    push @to_write, $effect_addresses{$effect}->[3];
	    $last_fired{$effect} = time();
            print "fire: $effect, $state\n";
	} elsif($state eq 'high') {
	    push @to_write, $effect_addresses{$effect}->[1];
	    $last_fired{$effect} = time();
            print "fire: $effect, $state\n";
	} elsif($state eq 'off' && $i%100 == 0) {
	    push @to_write, $effect_addresses{$effect}->[2];
	    push @to_write, $effect_addresses{$effect}->[0];
	    push @to_write, $effect_addresses{$effect}->[0];
	    push @to_write, $effect_addresses{$effect}->[2];
            print "fire: $effect, $state (anyway)\n";
	    # already off.
	    #print "off from off\n";
	}
    } elsif($effect_state{$effect} eq 'low') {
	# low effect is on.
	if($state eq 'low') {
	    # do nothing.
	} elsif($state eq 'high') {
	    push @to_write, $effect_addresses{$effect}->[2];
	    push @to_write, $effect_addresses{$effect}->[1];
	    $last_fired{$effect} = time();
            print "fire: $effect, $state\n";
	} elsif(($last_fired{$effect} + $min_firing_time) <= time()) {
	    # don't shut off unless the effect was fired more than 40ms ago.
	    #print "off from low\n";
	    push @to_write, $effect_addresses{$effect}->[2];
	    push @to_write, $effect_addresses{$effect}->[0];
	    push @to_write, $effect_addresses{$effect}->[0];
	    push @to_write, $effect_addresses{$effect}->[2];
            print "fire: $effect, $state\n";
	} else {
	  return;
        }
    } elsif($effect_state{$effect} eq 'high') {
	# high effect is on.
	if($state eq 'low') {
	    push @to_write, $effect_addresses{$effect}->[0];
	    push @to_write, $effect_addresses{$effect}->[3];
	    $last_fired{$effect} = time();
            print "fire: $effect, $state\n";
	} elsif($state eq 'high') {
	    # do nothing.
	} elsif(($last_fired{$effect} + $min_firing_time) <= time()) {
	    #print "off from high\n";
	    push @to_write, $effect_addresses{$effect}->[0];
	    push @to_write, $effect_addresses{$effect}->[2];
	    push @to_write, $effect_addresses{$effect}->[0];
	    push @to_write, $effect_addresses{$effect}->[2];

            print "fire: $effect, $state\n";
	} else {
	  return;
        }
    }
    
    $effect_state{$effect} = $state;
}



sub calibrate {
  my ($if, @sensors) = @_;
  foreach(1..500) {
    my @read_sensors = @sensors;
    my @write_sensors = map { $sensor_addresses{$_} } @sensors;
    my $first = 1;

    $if->write(pack('C*', @write_sensors));
    pop @read_sensors;

    while(@read_sensors) {
      my ($count, $raw_read) = $if->read(255);
      foreach my $value (unpack('C*', $raw_read)) {
        if($first) {
          undef $first;
          next;
        }
        my $sensor = shift @read_sensors;
        push @{$baseline_values{$sensor}}, { val => $value, time => time() };
        print ".";
      }
    }
  }
}
